// ========== Copyright Header Begin ==========================================
// 
// OpenSPARC T1 Processor File: Ni_AsiDispatcher.cc
// Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
// 
// The above named program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License version 2 as published by the Free Software Foundation.
// 
// The above named program is distributed in the hope that it will be 
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with this work; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
// 
// ========== Copyright Header End ============================================
/************************************************************************
**  
**  Copyright (C) 2002, Sun Microsystems, Inc.
**
**  Sun considers its source code as an unpublished, proprietary
**  trade secret and it is available only under strict license provisions.
**  This copyright notice is placed here only to protect Sun in the event 
**  the source is deemed a published work. Disassembly, decompilation,
**  or other means of reducing the object code to human readable form
**  is prohibited by the license agreement under which this code is
**  provided to the user or company in possession of this copy."
**
*************************************************************************/
#include "AsiHandler.h"
#include "AsiDispatchPacket.h"
#include "Ni/Ni_AsiDispatcher.h"
#include "Ni/Ni_AsiDispatchTable.h"
#include "Ni/Ni_Asi.h"

using namespace std;
using namespace Riesling;

Ni_AsiDispatcher::Ni_AsiDispatcher( AsiDispatchTable *table )
 :
  AsiDispatcher()
{
    list<AsiDispatchTable::AsiDispatchEntry*>::iterator i, end;
	
    if( table ){
	i   = table->asiAssignment_.begin();
	end = table->asiAssignment_.end();
    } else {
	if( defaultAsiAssignments == NULL ){
	    defaultAsiAssignments = new Ni_AsiDispatchTable;
	    defaultAsiAssignments->initDefaultTable();
	}
	i   = defaultAsiAssignments->asiAssignment_.begin();
	end = defaultAsiAssignments->asiAssignment_.end();
    }

    for( ; i != end; ++i ){
	AsiDispatchEntry *newEntry = new AsiDispatchEntry( NULL, (*i)->id_, 
		(*i)->asi_, (AsiAccessT)(*i)->access_, (AsiPrivT)(*i)->priv_,
		(*i)->vaddrStart_, (*i)->vaddrEnd_ - (*i)->vaddrStart_ );
	addAsiAccess( (*i)->className_, newEntry );
    }
}

// TODO these defines will be removed when asi.xml handles valid opcode specification

#define ASI_ICACHE_INSTR              0x66
#define ASI_ICACHE_TAG                0x67
#define ASI_ICACHE_PRE_DECODE         0x6E
#define ASI_ICACHE_NEXT_FIELD         0x6F

////////////////////////////////////////////////////////

void
Ni_AsiDispatcher::read( AsiDispatchPacket &packet, AsiPrivT priv )
{
    AsiDispatchEntry *entry = lookup( packet.getAsi(), packet.getVaddr(), READ  );
    AsiObject *asiObj = packet.getAsiObject();
    AccessT allowedAccess = asiObj->getAccess( packet.getAsi(), packet.getVaddr(), priv );
    bool acc;
    bool p;
    bool error = false;

    uint64_t vaddr = packet.getVaddr();
    uint64_t alignMask;

    AsiT asi = packet.getAsi();

    InstructionWord iw;

    if( packet.getInstruction( iw ) == false ){
	RIESLING_THROW_DOMAIN_ERROR( "No valid instruction word for this ASI transaction" );
    }

    if( iw.isPrefetch() ){
	packet.setResponse( AsiDispatchPacket::OK );
	return;
    }

    bool validOpcode = false;

    InstructionWord::OpcodeT  opcode = iw.getOpcode();

    alignMask = iw.getInstAlignSize() - 1;

    if( (vaddr & alignMask) != 0 ){
	if( ((vaddr & 0x7) == 0x4) && (opcode == InstructionWord::LDDFA) ){
	    packet.setResponse( AsiDispatchPacket::LDDF_MEM_ADDRESS_NOT_ALIGNED );
	} else {
	    packet.setResponse( AsiDispatchPacket::MEM_ADDRESS_NOT_ALIGNED );
	}
    }

    switch( asi ){
	case ASI_ICACHE_INSTR:
	case ASI_ICACHE_TAG:
	{
	    switch( opcode ){
		case InstructionWord::LDXA: 
		case InstructionWord::STXA:
		    validOpcode = true;
		    break;
		default:
		    validOpcode = false;
		    break;
	    }
	}
	break;

	default:
	{
	    switch( opcode ){
		case InstructionWord::LDXA: 
		case InstructionWord::STXA: 
//		case InstructionWord::LDDFA: 		// MMU-25
//		case InstructionWord::STDFA:
		    validOpcode = true;
		    break;
		default:
		    validOpcode = false;
		    break;
	    }
	}
	break;
    }

    if( (priv == USER) && !(asi & 0x80) ) {
	packet.setResponse( AsiDispatchPacket::PRIV_EXCEPTION );	// priority 11

    }

    if( isAsiDataAccessException(asi, vaddr, 0, priv) ){
	packet.setResponse( AsiDispatchPacket::INVALID_ACCESS );	// priority 12
	return;
    }

    if((validOpcode == false) ){
	packet.setResponse( AsiDispatchPacket::INVALID_OPCODE );
    }

    if( !(allowedAccess & READ) ){
	packet.setResponse( AsiDispatchPacket::INVALID_ACCESS );
    }

// the following check is from AsiDispatcher.cc and doesn't
// work very well for Niagara due to hypervisor support.
// this case should have been caught by the (asi & 0x80) check
// above. access to asi 0x30 - 0x7f by supervisor causes 
// data_access_exception trap, not priv exception
// jan22/2004
//
//    if( allowedAccess == ILLEGAL ){  
//	packet.setResponse( AsiDispatchPacket::PRIV_EXCEPTION );
//    }

    if( !entry ){
	if( packet.getResponse() == AsiDispatchPacket::NOT_INIT ){
		packet.setResponse( AsiDispatchPacket::NOT_MAPPED );
	}
    } else {
	if( packet.getResponse() == AsiDispatchPacket::NOT_INIT ){
	    // Handler should set the return code to OK or HANDLER_ERROR
	    packet.setAccessIndex( entry->getDataId() );
	    entry->getHandlingInstance()->asiReadHandler( packet );
	    // check for correct setting of return code.
	    if( !packet.isRespCodeSet( AsiDispatchPacket::OK ) &&
		!packet.isRespCodeSet( AsiDispatchPacket::HANDLER_ERROR ) ){
		RIESLING_THROW_DOMAIN_ERROR( "ASI Handler did properly set a response code" );
	    }
	} else if( error && packet.getAsiObject()->errToHandler( packet.getAsi() ) ){
	    entry->getHandlingInstance()->errorHandler( packet );
	}
    }
}

////////////////////////////////////////////////////////


void
Ni_AsiDispatcher::write( AsiDispatchPacket &packet, AsiPrivT priv )
{
    AsiDispatchEntry *entry = lookup( packet.getAsi(), packet.getVaddr(), WRITE  );
    bool acc;
    bool p;
    bool error = false;
    AsiObject *asiObj = packet.getAsiObject();
    AccessT allowedAccess = asiObj->getAccess( packet.getAsi(), packet.getVaddr(), priv );

    uint64_t vaddr = packet.getVaddr();
    uint64_t alignMask;

    AsiT asi = packet.getAsi();

    InstructionWord iw;

    if( packet.getInstruction( iw ) == false ){
	RIESLING_THROW_DOMAIN_ERROR( "No valid instruction word for this ASI transaction" );
    }

    if( iw.isPrefetch() ){
	packet.setResponse( AsiDispatchPacket::OK );
	return;
    }

    bool validOpcode = false;

    InstructionWord::OpcodeT  opcode = iw.getOpcode();

    alignMask = iw.getInstAlignSize() - 1;

    if( (vaddr & alignMask) != 0 ){

	if( ((vaddr & 0x7) == 0x4) && (opcode == InstructionWord::STDFA) ){
	    packet.setResponse( AsiDispatchPacket::STDF_MEM_ADDRESS_NOT_ALIGNED );
	} else {
	    packet.setResponse( AsiDispatchPacket::MEM_ADDRESS_NOT_ALIGNED );
	}
	error = true;
    }

    switch( asi ){
	case ASI_ICACHE_INSTR:
	case ASI_ICACHE_TAG:
	{
	    switch( opcode ){
		case InstructionWord::LDXA: 
		case InstructionWord::STXA:
		    validOpcode = true;
		    break;
		default:
		    validOpcode = false;
		    break;
	    }
	}
	break;

	default:
	{
	    switch( opcode ){
		case InstructionWord::LDXA: 
		case InstructionWord::STXA: 
//		case InstructionWord::LDDFA: 	// MMU-25
//		case InstructionWord::STDFA:
		    validOpcode = true;
		    break;
		default:
		    validOpcode = false;
		    break;
	    }
	}
	break;
    }

    if( (priv == USER) && !(asi & 0x80) ) {
	packet.setResponse( AsiDispatchPacket::PRIV_EXCEPTION );	// priority 11
    } 

    if( isAsiDataAccessException(asi, vaddr, 1, priv) ){
	packet.setResponse( AsiDispatchPacket::INVALID_ACCESS );	// priority 12
    }

    if((validOpcode == false) ){
	packet.setResponse( AsiDispatchPacket::INVALID_OPCODE );
    }

    if( !(allowedAccess & WRITE) ){
	packet.setResponse( AsiDispatchPacket::INVALID_ACCESS );
    }

// the following check is from AsiDispatcher.cc and doesn't
// work very well for Niagara due to hypervisor support.
// this case should have been caught by the (asi & 0x80) check
// above. access to asi 0x30 - 0x7f by supervisor causes 
// data_access_exception trap, not priv exception
// jan22/2004
//
//    if( allowedAccess == ILLEGAL ){  
//	packet.setResponse( AsiDispatchPacket::PRIV_EXCEPTION );
//    }

    if( !entry ){
	if( packet.getResponse() == AsiDispatchPacket::NOT_INIT ){
	    packet.setResponse( AsiDispatchPacket::NOT_MAPPED );
	}
    } else {
	if( packet.getResponse() == AsiDispatchPacket::NOT_INIT ){
	    // Handler should set the return code to OK or HANDLER_ERROR
	    packet.setAccessIndex( entry->getDataId() );
	    entry->getHandlingInstance()->asiWriteHandler( packet );
	    // check for correct setting of return code.
	    if( !packet.isRespCodeSet( AsiDispatchPacket::OK ) &&
		!packet.isRespCodeSet( AsiDispatchPacket::HANDLER_ERROR ) ){
		RIESLING_THROW_DOMAIN_ERROR( "ASI Handler did properly set a response code" );
	    }
	} else if( error && packet.getAsiObject()->errToHandler( packet.getAsi() ) ){
	    entry->getHandlingInstance()->errorHandler( packet );
	}
    }
}

////////////////////////////////////////////////////////
#define IN_RANGE(x, lo, hi)	( ((x >= lo) && (x <= hi)) ? true : false )

bool 
Ni_AsiDispatcher::isAsiDataAccessException( AsiT asi, VaddrT vaddr, int isWr, AsiPrivT priv )
{

    bool exc = false;

    if( priv == USER ){
	if( IN_RANGE(asi, 0xc0, 0xe1) ) {
	    exc = true;
	}
    } else if (priv == PRIV) {
	if( IN_RANGE(asi, 0x30, 0xe1) ) {
	    exc = true;
	}	

				// QUEUE TAIL pointers are read-only by supervisor

	if( isWr == 1 && asi==Ni_Asi::ASI_QUEUE ) {
	    if( vaddr == 0x3c8 ||	// CPU_MONDO_TAIL 
	        vaddr == 0x3d8 ||	// DEV_MONDO_TAIL
	        vaddr == 0x3e8 ||	// RESUMABLE_TAIL
	        vaddr == 0x3f8 )	// NONRESUMABLE_TAIL )
	    {
	        exc = true;
	    }
	}

    } else if (priv == HYPER) {
	if( IN_RANGE(asi, 0x76, 0xe1) ) {
	    exc = true;
	}	

    }


   if( (asi == Ni_Asi::ASI_SWVR_UDB_INTR_W) && (isWr == 0) ) {		
	exc = true;	// probably don't need this here anymore, should have been
			// caught in Ni_AsiDispatcher.cc
			// Feb4, 2004
   }


    return exc;

}
